﻿//     Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Gateway.Configuration;
using Microsoft.Gateway.RulesConfigurator.ConfigComparitors;
using Microsoft.Gateway.Utils;
using Microsoft.Gateway.Utils.Update;

namespace Microsoft.Gateway.RulesConfigurator
{
    public enum ConfiguratorResults
    {
        NoChange,
        Failure,
        Success,
        RetryLater,
    }

    public class Program
    {
        const string ConfigKeyCacheLocation = "CacheLocation";
        const string ConfigKeyNoUpdateCheck = "NoUpdateCheck";
        const string LogFilename            = "GatewayConfiguration.log";

        static int Main(string[] args)
        {
            bool addLogListener = false;
            if (!Trace.Listeners.OfType<ConsoleTraceListener>().Any())
            {
                Trace.Listeners.Add(new ConsoleTraceListener());
                addLogListener = true;
            }
            // Required to point to the correct authentication endpoints
            AzureEnvironment.InitializeEnvironment(AzureUtils.GetConfigSetting("AzureEnvironment", AzureEnvironments.AzureCloud));
            AzureUtils.AddAzureDiagnosticsListener();
            if (args.Length < 1)
            {
                Trace.TraceError("Invalid arguments. Usage: RulesConfiguration {ConfigurationLocation} [ConfigurationCache]");
                return 1;
            }
            string storageLocation = args[0];
            string cacheLocation = null;
            if (args.Length >= 2)
            {
                cacheLocation = args[1];
                // Update the config file so that we don't need to keep specifying this argument
                try
                {
                    var appConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                    var settings = appConfig.AppSettings.Settings;
                    var cacheLocationElement = settings[ConfigKeyCacheLocation];
                    if (cacheLocationElement == null)
                    {
                        settings.Add(ConfigKeyCacheLocation, cacheLocation);
                    }
                    else
                    {
                        cacheLocationElement.Value = cacheLocation;
                    }
                    appConfig.Save(ConfigurationSaveMode.Full);
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Failed to update application configuration. Details: {0}", ex);
                    return 5;
                }
            }
            if (String.IsNullOrWhiteSpace(cacheLocation))
            {
                cacheLocation = Environment.ExpandEnvironmentVariables(AzureUtils.GetConfigSetting(ConfigKeyCacheLocation, "%temp%"));
            }
            if (String.IsNullOrWhiteSpace(cacheLocation))
            {
                Trace.TraceError("Invalid cache location");
                return 2;
            }
            if (addLogListener)
            {
                Trace.Listeners.Add(new TextWriterTraceListener(Path.Combine(cacheLocation, LogFilename)));
            }
            Trace.TraceInformation("RulesConfigurator (Version: {0}): Reading routing rules configuration from storage account [{1}].",
                Assembly.GetEntryAssembly().GetName().Version, storageLocation);
            if (!AzureUtils.GetConfigSetting(ConfigKeyNoUpdateCheck, false))
            {
                CheckForUpdate(cacheLocation);
            }
            RulesConfiguration downloadedConfiguration = null;
            try
            {
                downloadedConfiguration = ConfigurationLoader.ReadFromSource(storageLocation);
            }
            catch (Exception ex)
            {
                Trace.TraceError("Failed to retrieve remote Gateway configuration from [{0}]. Details: {1}", storageLocation, ex);
                return 3;
            }
            RulesConfiguration cachedConfiguration = null;
            try
            {
                cachedConfiguration = ConfigurationLoader.ReadFromCache(cacheLocation);
            }
            catch (Exception ex)
            {
                Trace.TraceWarning("Failed to compare cached configuration. Details: {0}", ex);
            }
            var configApplicator = ApplyConfigurationUpdates(cachedConfiguration, downloadedConfiguration);
            if (configApplicator.FailedExitCode != 0)
            {
                return configApplicator.FailedExitCode;
            }
            else if (configApplicator.RetryLater)
            {
                Trace.TraceInformation("New configuration could not be applied at this time. Configuration will be retried shortly.");
            }
            else if (configApplicator.UpdateCache)
            {
                ConfigurationLoader.WriteToCache(downloadedConfiguration, cacheLocation);
                Trace.TraceInformation("Updated configuration has been applied to web site");
            }
            else
            {
                Trace.TraceInformation("Configuration has not been updated. No changes have been applied.");
            }
            Trace.Close();

            return 0;
        }

        public static ConfiguratorState ApplyConfigurationUpdates(RulesConfiguration cachedConfiguration, RulesConfiguration updatedConfiguration, string configPath = null)
        {
            var configApplicator = new ConfiguratorState(cachedConfiguration, updatedConfiguration);
            try
            {
                configApplicator.ApplyConfiguration<RulesComparitor>(
                    (config) => WebsiteConfigurator.ApplyConfiguration(config, configPath), 4);
                configApplicator.ApplyConfiguration<AuthNComparitor>(
                    (config) => DirectoryConfigurator.ApplyConfiguration(config.Directory, config.ApplicationName, configPath), 5);
                configApplicator.ApplyConfiguration<AuthZComparitor>(
                    (config) => AuthorizationConfigurator.ApplyConfiguration(config, configPath), 6);
            }
            catch (ApplicationException)
            {
                // Trace message already logged
                if (configApplicator.FailedExitCode == 0)
                {
                    throw;
                }
            }
            return configApplicator;
        }

        static void CheckForUpdate(string cacheLocation)
        {
            int updateCheckMinute = SharedCache.ReadCachedObject<int>(SharedCache.CacheClasses.UpdateCheckSeed, cacheLocation);
            if (updateCheckMinute == 0)
            {
                updateCheckMinute = DateTime.UtcNow.Minute + 1;
                SharedCache.WriteObjectToCache(SharedCache.CacheClasses.UpdateCheckSeed, updateCheckMinute, cacheLocation);
            }
            if (DateTime.UtcNow.Minute == updateCheckMinute - 1)
            {
                var updateManifest = UpdateClient.GetAvailableUpdate(UpdateClient.Components.Gateway,
                    null,
                    AzureUtils.GetConfigSetting("UpdateServiceUri", String.Empty));
                if (updateManifest != null)
                {
                    // Can't do delegates on Trace methods as they're marked [Conditional]. 
                    string logMsg = String.Format("An new {0} update is available for the Service Gateway, version: [{1}]. Update details: {2}",
                        updateManifest.Severity, updateManifest.Version.GatewayFormat(), updateManifest.Description);
                    if (updateManifest.Severity == UpdateSeverity.Optional)
                    {
                        Trace.TraceInformation(logMsg);
                    }
                    else
                    {
                        Trace.TraceWarning(logMsg);
                    }
                }
            }
        }
    }
}
